﻿namespace Nomo.UnityCoreModule.UI
{
    /// <summary>
    /// 滚动布局组。
    /// </summary>
    [UnityEngine.RequireComponent(typeof(UnityEngine.UI.RectMask2D))]
    public sealed class ScrollLayoutGroup : UnityEngine.UI.LayoutGroup
    {
        /// <summary>
        /// 是否让子物体沿水平方向排列？
        /// </summary>
        public bool horizontal;

        [UnityEngine.SerializeField] private float centerIndex;

        private UnityEngine.UI.RectMask2D _rectMask2D;
        private UnityEngine.Vector2       _maxMinSize       = UnityEngine.Vector2.zero;
        private UnityEngine.Vector2       _maxPreferredSize = UnityEngine.Vector2.zero;
        private UnityEngine.Vector2       _maxFlexibleSize  = UnityEngine.Vector2.zero;

        /// <summary>
        /// 获取或设置位于此布局组的中心的布局元素的索引。
        /// </summary>
        public float CenterIndex { get => centerIndex; set => SetCenterIndex(value); }

        /// <summary>
        /// 此组件所在游戏物体上的二维矩形遮罩。
        /// </summary>
        public UnityEngine.UI.RectMask2D RectMask2D => _rectMask2D != null ? _rectMask2D : _rectMask2D = GetComponent<UnityEngine.UI.RectMask2D>();

        /// <summary>
        /// 参与布局的子物体数量。
        /// </summary>
        public int LayoutChildrenCount => rectChildren.Count;

        private static void GetChildSizes(UnityEngine.RectTransform child, int axis, out float min, out float preferred, out float flexible)
        {
            min       = UnityEngine.UI.LayoutUtility.GetMinSize(child, axis);
            preferred = UnityEngine.UI.LayoutUtility.GetPreferredSize(child, axis);
            flexible  = UnityEngine.UI.LayoutUtility.GetFlexibleSize(child, axis);
        }

        private void SetCenterIndex(float value)
        {
            if (centerIndex == value)
            {
                return;
            }
            centerIndex = value;
            SetDirty();
        }

        /// <summary>
        /// 将指定的是布局元素的子物体居中。
        /// </summary>
        /// <param name="child">一个是布局元素的子物体。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="child"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="System.ArgumentException"><paramref name="child"/> 不是布局元素，或不是这个矩形变换的子物体。</exception>
        public void SetLayoutChildToCenter(UnityEngine.RectTransform child)
        {
            SetCenterIndex(GetLayoutChildIndex(child));
        }

        /// <summary>
        /// 获取指定的是布局元素的子物体在此布局组的所有布局元素中的索引。
        /// </summary>
        /// <param name="child">一个是布局元素的子物体。</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException"><paramref name="child"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="System.ArgumentException"><paramref name="child"/> 不是布局元素，或不是这个矩形变换的子物体。</exception>
        public int GetLayoutChildIndex(UnityEngine.RectTransform child)
        {
            if (child == null)
            {
                throw new System.ArgumentNullException(nameof(child));
            }

            if (!rectChildren.Contains(child))
            {
                throw new System.ArgumentException();
            }

            return rectChildren.IndexOf(child);
        }

        private void PreCalculation()
        {
            _maxMinSize       = default;
            _maxPreferredSize = default;
            _maxFlexibleSize  = default;
            foreach (var child in rectChildren)
            {
                GetChildSizes(child, 0, out var min, out var preferred, out var flexible);
                _maxMinSize[0]       = UnityEngine.Mathf.Max(_maxMinSize[0],       min);
                _maxPreferredSize[0] = UnityEngine.Mathf.Max(_maxPreferredSize[0], preferred);
                _maxFlexibleSize[0]  = UnityEngine.Mathf.Max(_maxFlexibleSize[0],  flexible);
                GetChildSizes(child, 1, out min, out preferred, out flexible);
                _maxMinSize[1]       = UnityEngine.Mathf.Max(_maxMinSize[1],       min);
                _maxPreferredSize[1] = UnityEngine.Mathf.Max(_maxPreferredSize[1], preferred);
                _maxFlexibleSize[1]  = UnityEngine.Mathf.Max(_maxFlexibleSize[1],  flexible);
            }
        }

        /// <inheritdoc />
        public override void CalculateLayoutInputHorizontal()
        {
            PreCalculation();
            base.CalculateLayoutInputHorizontal();
            SetLayoutInputForAxis(_maxMinSize[0] + padding.horizontal, _maxPreferredSize[0] + padding.horizontal, _maxFlexibleSize[0], 0);
        }

        /// <inheritdoc />
        public override void CalculateLayoutInputVertical()
        {
            SetLayoutInputForAxis(_maxMinSize[1] + padding.vertical, _maxPreferredSize[1] + padding.vertical, _maxFlexibleSize[1], 1);
        }

        /// <inheritdoc />
        public override void SetLayoutHorizontal()
        {
            switch (horizontal)
            {
                case false:
                    foreach (var rectChild in rectChildren)
                    {
                        GetChildSizes(rectChild, 0, out _, out var preferred, out _);
                        SetChildAlongAxis(rectChild, 0, GetStartOffset(0, preferred), preferred);
                    }
                    break;
                default:
                    var maxPreferredWidth = _maxPreferredSize[0];
                    for (var i = 0; i < rectChildren.Count; i++)
                    {
                        var child = rectChildren[i];
                        GetChildSizes(child, 0, out _, out var preferred, out _);
                        var pos = GetStartOffset(0, preferred) + (padding.horizontal + maxPreferredWidth) * (i - centerIndex);
                        SetChildAlongAxis(child, 0, pos, preferred);
                    }
                    break;
            }
        }

        /// <inheritdoc />
        public override void SetLayoutVertical()
        {
            switch (horizontal)
            {
                case false:
                    var maxPreferredHeight = _maxPreferredSize[1];
                    for (var i = 0; i < rectChildren.Count; i++)
                    {
                        var child = rectChildren[i];
                        GetChildSizes(child, 1, out _, out var preferred, out _);
                        var pos = GetStartOffset(1, preferred) + (padding.vertical + maxPreferredHeight) * (i - centerIndex);
                        SetChildAlongAxis(child, 1, pos, preferred);
                    }
                    break;
                default:
                    foreach (var rectChild in rectChildren)
                    {
                        GetChildSizes(rectChild, 1, out _, out var preferred, out _);
                        SetChildAlongAxis(rectChild, 1, GetStartOffset(1, preferred), preferred);
                    }
                    break;
            }
        }
    }
}
